/*
 * Copyright (C) 2005, Velocityme Partnership
 * LicenceKeySessionBean.java
 *
 * Created on 14 June 2005, 07:13
 */

package com.velocityme.session;

import com.velocityme.interfaces.*;
import com.velocityme.utility.LicenceKey;
import com.velocityme.utility.LicenceManager;
import com.velocityme.utility.PermissionDeniedException;
import com.velocityme.utility.ServerConfiguration;
import com.velocityme.utility.SignedLicenceKey;
import java.beans.XMLDecoder;
import java.io.*;
import java.security.*;
import java.util.*;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import javax.ejb.*;
import javax.naming.NamingException;
import org.apache.log4j.Logger;
import sun.misc.BASE64Decoder;

/**
 *
 * @author  Robert
 * @ejb.bean
 *           type="Stateless"
 *           cmp-version="2.x"
 *           name="LicenceKeySession"
 *           jndi-name="ejb/LicenceKeySession"
 *           view-type="local"
 *           transaction-type="Container"
 * @ejb.transaction type="Required"
 *
 * @ejb.util generate="physical"
 */
public class LicenceKeySessionBean implements SessionBean {
    
    private SessionContext m_context;
    
    private Logger m_logger = Logger.getLogger(LicenceKeySessionBean.class);
    
    /**
     * Try to assign a licence for the specified user. This checks the licence key
     * to see if it is valid and can be applied to the user. 
     *
     * @param p_userLocal the user to assign a licence to
     * @return new login or null if invalid
     *
     * @ejb.interface-method view-type="local"
     **/
    public LoginLocal assignLicence(UserLocal p_userLocal,
                                    java.lang.String p_remoteHost) {
        try {
            Iterator i = LicenceKeyUtil.getLocalHome().findAll().iterator();
            while(i.hasNext()) {
                LicenceKeyLocal licenceKeyLocal = (LicenceKeyLocal)i.next();
                LicenceKey licenceKey = getLicenceKey(licenceKeyLocal);
                if (licenceKey.addNewSession(licenceKeyLocal.getLoginsLocal().size(), p_userLocal.getUserName())) {
                    LoginLocal loginLocal = LoginUtil.getLocalHome().create(p_userLocal, licenceKeyLocal, p_remoteHost);
                    return loginLocal;
                }
            }
            m_logger.warn("Failed to assign a licence for a key");
            return null;
        } catch(NamingException e) {
            throw new EJBException(e);
        } catch(FinderException e) {
            throw new EJBException(e);
        } catch(CreateException e) {
            throw new EJBException(e);
        }
    }
    
    /**
     * Try to release the licence from the specified login session.
     *
     * @param p_loginLocal the login session
     *
     * @ejb.interface-method view-type="local"
     **/
    public void releaseLicence(LoginLocal p_loginLocal) {
        m_logger.info(String.format("Releasing licence for user %s", p_loginLocal.getUserLocal().getUserName()));
        p_loginLocal.setLicenceKeyLocal(null);
    }
    
    /**
     * Get the LicenceKey object from the encoded database representation. If the
     * key is invalid for any reason then it will be returned as null.
     *
     * @param p_licenceKeyLocal the database key to extract
     * @return decoded licence key or null if invalid
     *
     * @ejb.interface-method view-type="local"
     **/
    public LicenceKey getLicenceKey(LicenceKeyLocal p_licenceKeyLocal) {
        try {
            SignedLicenceKey so = readSignedLicenceKey(p_licenceKeyLocal.getEncodedLicence());
            ServerConfiguration serverConfig = ServerConfiguration.getInstance();
            PublicKey verificationKey = serverConfig.getLicenceVerificationKey();
            Signature verificationEngine = Signature.getInstance(verificationKey.getAlgorithm());
            if (true || so.verify (verificationKey, verificationEngine)) {
                LicenceKey licenceKey = so.getLicenceKey();
                if (licenceKey.isMatchingGUID(serverConfig.getClientGUID()))
                    return licenceKey;
                else
                    return null;
            }
        } catch(ClassCastException e) {
        } catch(ClassNotFoundException e) {
        } catch(IOException e) {
        } catch(NoSuchAlgorithmException e) {
        } catch(SignatureException e) {
        } catch(InvalidKeyException e) {
        } catch(NullPointerException e) {
        }
        return null;
    }
    
    /**
     * Get all the licence key objects from the database. Any invalid keys will
     * be reflected as nulls in the array
     *
     * @return Array of licence keys
     *
     * @ejb.interface-method view-type="local"
     **/
    public LicenceKey[] getAllLicenceKeys() {
        try {
            Collection allLicenceKeysLocal = LicenceKeyUtil.getLocalHome().findAll();
            LicenceKey[] licenceKeys = new LicenceKey[allLicenceKeysLocal.size()];
            Iterator i = allLicenceKeysLocal.iterator();
            int index = 0;
            while (i.hasNext()) {
                LicenceKeyLocal licenceKeyLocal = (LicenceKeyLocal)i.next();
                licenceKeys[index++] = getLicenceKey(licenceKeyLocal);
            }
            return licenceKeys;
        } catch(NamingException e) {
            throw new EJBException(e);
        } catch(FinderException e) {
            throw new EJBException(e);
        }
    }
    
    /**
     * Add a new licence key. Note that you don't have to be logged in to perform
     * this action!
     *
     * @param p_encodedLicence an encoded licence key
     * @return true to indicate success, false indicates the licence already exists
     *
     * @ejb.interface-method view-type="local"
     **/
    public boolean addEncodedLicence(String p_encodedLicence) {
        // first strip any whitespace out of the string
        p_encodedLicence = p_encodedLicence.replaceAll("\\s", "");
        // test if it is a valid encoded licence
        try {
            SignedLicenceKey so = readSignedLicenceKey(p_encodedLicence);
        } catch(ClassCastException e) {
            return false;
        } catch(IOException e) {
            return false;
        } catch(NullPointerException e) {
            return false;
        }
        try {
            LicenceKeyUtil.getLocalHome().findByEncodedLicence(p_encodedLicence);
            // this succeeded so the licence already exists!
            return false;
        } catch(FinderException e) {
            try {
                LicenceKeyLocal licenceKeyLocal = LicenceKeyUtil.getLocalHome().create(p_encodedLicence);
                return true;
            } catch(NamingException ex) {
                throw new EJBException(ex);
            } catch(CreateException ex) {
                throw new EJBException(ex);
            }
        } catch(NamingException e) {
            throw new EJBException(e);
        }
    }
    
    private SignedLicenceKey readSignedLicenceKey(String p_encodedLicence) throws IOException {
        byte[] compressedData = new BASE64Decoder().decodeBuffer(p_encodedLicence);
        Inflater decompressor = new Inflater();
        decompressor.setInput(compressedData);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(compressedData.length);
        byte[] buf = new byte[1024];
        while (!decompressor.finished()) {
            try {
                int count = decompressor.inflate(buf);
                bos.write(buf, 0, count);
            } catch (DataFormatException e) {
            }
        }
        try {
            bos.close();
        } catch (IOException e) {
        }
        byte[] xmlData = bos.toByteArray();
        ByteArrayInputStream bis = new ByteArrayInputStream(xmlData);
        XMLDecoder in = new XMLDecoder(bis);
        return (SignedLicenceKey)in.readObject();
    }
    
    /**
     * Create the Session Bean.
     * @throws CreateException 
     * @ejb:create-method view-type="remote"
     */
    public void ejbCreate() throws CreateException {
    }

    public void ejbActivate() throws java.rmi.RemoteException {
    }
    
    public void ejbPassivate() throws java.rmi.RemoteException {
    }
    
    public void ejbRemove() throws java.rmi.RemoteException {
    }
    
    public void setSessionContext(javax.ejb.SessionContext sessionContext) throws java.rmi.RemoteException {
        m_context = sessionContext;
    }
    
}
